/* Copyright (c) 2014 Wolfgang Imig This file is part of the library "Java Add-in for Microsoft Office". This file must be used according to the terms of MIT License, http://opensource.org/licenses/MIT */ package com.wilutions.joa.example.cnaddin; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.NumberBinding; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.RowConstraints; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Line; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import com.sun.javafx.scene.control.behavior.TextAreaBehavior; import com.sun.javafx.scene.control.skin.TextAreaSkin; import com.wilutions.com.BackgTask; import com.wilutions.com.ComException; import com.wilutions.com.reg.DeclRegistryValue; import com.wilutions.com.reg.RegUtil; import com.wilutions.joa.fx.TaskPaneFX; import com.wilutions.joa.outlook.fx.CategoryItem; import com.wilutions.mslib.office.IRibbonUI; import com.wilutions.mslib.office.MsoCTPDockPositionRestrict; import com.wilutions.mslib.office._CustomTaskPane; import com.wilutions.mslib.outlook.Categories; import com.wilutions.mslib.outlook.Inspector; import com.wilutions.mslib.outlook.MailItem; import com.wilutions.mslib.outlook.NoteItem; import com.wilutions.mslib.outlook.OlItemType; public class NoteTaskPane extends TaskPaneFX { private Inspector inspector; private GridPane noteGrid; private TextArea noteText; private CheckBox ckDisplay; private CheckBox ckCatMail; private MailItem mailItem; private Label noteHeaderLabel; private Timer timer; private List<CategoryItem> catList; private com.wilutions.mslib.outlook.Application app; private Color noteGridDefaultBackgColor = Color.rgb(255, 255, 204); private final static Color noteHeaderColor = Color.rgb(153, 180, 209); private final String categoryListDelimiter; @DeclRegistryValue private boolean reg_displayNote = true; @DeclRegistryValue private boolean reg_alsoCategorizeMail = true; private enum NoteState { New, Modified, Saved } private NoteState noteState = NoteState.New; public NoteTaskPane(Inspector inspector) { this.inspector = inspector; this.categoryListDelimiter = (String) RegUtil.getRegistryValue("HKCU\\Control Panel\\International", "sList", ","); Globals.getThisAddin().getRegistry().readFields(this); } @Override public void onVisibleStateChange(final _CustomTaskPane ctp) throws ComException { Boolean visible = ctp.getVisible(); System.out.println("ctp.visible=" + visible); IRibbonUI ribbon = Globals.getThisAddin().getRibbon(); if (ribbon != null) { ribbon.InvalidateControl("ShowHideTP"); } } @Override public void close() { // timer, ckDisplay, ckCatMail is null, if the inspector window was closed, // before the scene has been created. if (timer != null) { timer.cancel(); reg_displayNote = ckDisplay.isSelected(); reg_alsoCategorizeMail = ckCatMail.isSelected(); } super.close(); Globals.getThisAddin().getRegistry().writeFields(this); } private static String toRGB(Color noteBackgColor) { String noteBackgRgb = String.format("#%02x%02x%02x", (int) (noteBackgColor.getRed() * 255), (int) (noteBackgColor.getGreen() * 255), (int) (noteBackgColor.getBlue() * 255)); return noteBackgRgb; } private static boolean isDarkColor(Color c) { double d = c.getRed() + c.getGreen() + c.getBlue(); d /= 3; return d < 0.5; } private void setNoteGridStyleIfUnsafed(GridPane noteGrid, Color noteBackgColor) { if (noteState != NoteState.Saved) { setNoteGridStyleAlways(noteGrid, noteBackgColor); } } private void setNoteGridStyleAlways(GridPane noteGrid, Color noteBackgColor) { String noteBackgRgb = toRGB(noteBackgColor); String backgStyle = "-fx-background-color: " + noteBackgRgb + "; -fx-control-inner-background: " + noteBackgRgb + "; -fx-border: 1px solid; -fx-border-color: black;"; noteGrid.setStyle(backgStyle); } private void updateNoteState(NoteState noteState) { this.noteState = noteState; String headerText = ""; switch (noteState) { case New: headerText = "*New*"; break; case Modified: headerText = "*Modified*"; break; case Saved: headerText = "-Saved-"; break; default: throw new IllegalStateException("Unknown NoteState=" + noteState); } final String s = headerText; Platform.runLater(() -> noteHeaderLabel.setText(s)); } @Override public Scene createScene() throws ComException { // The Outlook application interface app = Globals.getThisAddin().getApplication(); // Selected mail mailItem = inspector.getCurrentItem().as(MailItem.class); // Categories final Categories categories = app.GetNamespace("MAPI").getCategories(); catList = CategoryItem.createObservableListOfCategories(categories); catList.sort((p, q) -> p.toString().compareTo(q.toString())); // ------------------------------------------------------------------------ // The task panes position might be of interest for building the layout // of the scene: // MsoCTPDockPosition dockPosition = // super.customTaskPane.getDockPosition(); // int wd = super.customTaskPane.getWidth(); // int ht = super.customTaskPane.getHeight(); // Allow to dock the task pane only at the left and right border // of the inspector window customTaskPane.setDockPositionRestrict(MsoCTPDockPositionRestrict.msoCTPDockPositionRestrictNoHorizontal); // ------------------------------------------------------------------------ // Initialize note category with mail category String mailCats = mailItem.getCategories(); if (mailCats != null && mailCats.length() != 0) { String[] mailCatArr = mailCats.split(categoryListDelimiter); String mailCat = mailCatArr.length != 0 ? mailCatArr[mailCatArr.length - 1] : null; if (mailCat != null) { for (CategoryItem cat : catList) { if (cat.toString().equals(mailCat)) { noteGridDefaultBackgColor = cat.getColor(); break; } } } } // ------------------------------------------------------------------------ // Build the scene VBox root = new VBox(); root.setSpacing(4); final Scene scene = new Scene(root); scene.getStylesheets().add("com/wilutions/joa/example/cnaddin/stylesheet.css"); // ------------------------------------------------------------------------ // The note is displayed in a view based on a GridPane with three // columns int noteRowIdx = 0; noteGrid = new GridPane(); setNoteGridStyleIfUnsafed(noteGrid, noteGridDefaultBackgColor); noteGrid.setPadding(new Insets(2)); noteGrid.setVgap(2); root.getChildren().add(noteGrid); // ------------------------------------------------------------------------ // The note header bar int headerRectsSize = 16; noteGrid.getRowConstraints().add(new RowConstraints(headerRectsSize, headerRectsSize, headerRectsSize)); noteGrid.getColumnConstraints().add(new ColumnConstraints(headerRectsSize, headerRectsSize, headerRectsSize)); noteGrid.getColumnConstraints().add( new ColumnConstraints(headerRectsSize, 100, Double.MAX_VALUE, Priority.ALWAYS, HPos.CENTER, true)); noteGrid.getColumnConstraints().add(new ColumnConstraints(headerRectsSize, headerRectsSize, headerRectsSize)); // Note icon in left column Image img = new Image("com/wilutions/joa/example/cnaddin/NoteIcon.png"); ImageView noteLeftBox = new ImageView(img); noteLeftBox.setFitWidth(16); noteLeftBox.setFitHeight(16); // Note header label (*New*, *Modified*, -Saved-) VBox noteHeaderBar = new VBox(); GridPane.setMargin(noteHeaderBar, new Insets(8, 4, 8, 4)); noteHeaderBar.setAlignment(Pos.CENTER); noteHeaderLabel = new Label(); noteHeaderLabel.setStyle("-fx-background-color: " + toRGB(noteHeaderColor) + "; -fx-text-fill: white"); noteHeaderLabel.setMinHeight(16); noteHeaderLabel.setMaxHeight(16); noteHeaderLabel.setMinWidth(10); noteHeaderLabel.setMaxWidth(Double.MAX_VALUE); noteHeaderLabel.setAlignment(Pos.CENTER); updateNoteState(NoteState.New); noteHeaderBar.getChildren().add(noteHeaderLabel); // Right square Rectangle noteRightBox = new Rectangle(headerRectsSize, headerRectsSize, noteHeaderColor); noteGrid.add(noteLeftBox, 0, noteRowIdx); noteGrid.add(noteHeaderBar, 1, noteRowIdx); noteGrid.add(noteRightBox, 2, noteRowIdx); noteRowIdx++; // -------------------------------------------------------------------- // Edit box for note text noteText = new TextArea(); noteText.setPrefRowCount(10); noteText.setWrapText(true); String subject = mailItem.getSubject(); if (!subject.isEmpty()) { noteText.setText(subject + CRLF); } noteGrid.add(noteText, 0, noteRowIdx++, 3, 1); noteGrid.getRowConstraints().add(new RowConstraints()); // Add a change listener to set the header label to "*Modified*". noteText.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { updateNoteState(NoteState.Modified); } }); // The edit box should not eat the TAB key. This listener forwards // the focus to the next control, if the TAB key is pressed. noteText.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent event) { if (event.getCode() == KeyCode.TAB) { TextAreaSkin skin = (TextAreaSkin) noteText.getSkin(); if (skin.getBehavior() instanceof TextAreaBehavior) { TextAreaBehavior behavior = (TextAreaBehavior) skin.getBehavior(); if (event.isControlDown()) { behavior.callAction("InsertTab"); } else if (event.isShiftDown()) { behavior.callAction("TraversePrevious"); } else { behavior.callAction("TraverseNext"); } event.consume(); } } } }); // -------------------------------------------------------------------- // Line between edit box and date label Line noteBottomLine = new Line(); noteBottomLine.setStartX(0); noteBottomLine.setStartY(0); noteBottomLine.setEndX(0); noteBottomLine.setEndY(0); NumberBinding bottomLineBinding = Bindings.add(noteGrid.widthProperty(), 0); noteBottomLine.endXProperty().bind(bottomLineBinding.subtract(8)); noteGrid.add(noteBottomLine, 0, noteRowIdx++, 3, 1); noteGrid.getRowConstraints().add(new RowConstraints(3, 3, 3)); // -------------------------------------------------------------------- // Date label final Text noteDate = new Text(); noteDate.setStyle("-fx-font: 12px \"Courier\"; -fx-font-weight: bold"); noteGrid.getRowConstraints().add(new RowConstraints()); noteGrid.add(noteDate, 0, noteRowIdx++, 3, 1); // This timer updates the note date timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { Platform.runLater(() -> { DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT); ZonedDateTime date = ZonedDateTime.now(); noteDate.setText(date.format(dateFormatter)); }); } }, 0, 1000); // -------------------------------------------------------------------- // Checkbox "Display new note" ckDisplay = new CheckBox("Display new note"); ckDisplay.setSelected(reg_displayNote); root.getChildren().add(ckDisplay); // -------------------------------------------------------------------- // Checkbox "Assign category to mail" ckCatMail = new CheckBox("Assign category to mail"); ckCatMail.setSelected(reg_alsoCategorizeMail); root.getChildren().add(ckCatMail); // ------------------------------------------------------------------------ // Create a button for each category. for (CategoryItem cat : catList) { Button btn = new Button(cat.toString()); root.getChildren().add(btn); String style = "-fx-background-color: " + toRGB(cat.getColor()) + "; "; if (isDarkColor(cat.getColor())) { style += "-fx-text-fill: white;"; } btn.setStyle(style); btn.setMaxWidth(Double.MAX_VALUE); btn.setMaxHeight(20); btn.setCursor(Cursor.HAND); btn.setTooltip(new Tooltip("Create the note \n and assign it to category \"" + cat.toString() + "\".")); // This event handlers color the note view with the color of the // category btn.setOnMouseEntered((e) -> setNoteGridStyleIfUnsafed(noteGrid, cat.getColor())); btn.setOnMouseExited((e) -> setNoteGridStyleIfUnsafed(noteGrid, noteGridDefaultBackgColor)); // This action handler creates the note. btn.setOnAction(new CategoryButtonActionEvent(cat)); } return scene; } /** * Button click handler which creates the note. */ private class CategoryButtonActionEvent implements EventHandler<ActionEvent> { final CategoryItem category; public CategoryButtonActionEvent(CategoryItem category) { this.category = category; } @Override public void handle(ActionEvent event) { // Do not create a note, if the note text is empty. if (noteText.getText().trim().isEmpty()) return; // Set the note state to "saved". // Further mouse moves over the categories will no more update // the note's background. updateNoteState(NoteState.Saved); // Make sure, the correct background is assigned to the note. // If the note has been already saved, before this handler function // is executed, the mouse-enter function has not assigned // the right color to the note grid. setNoteGridStyleAlways(noteGrid, category.getColor()); BackgTask.run(() -> { try { // Create note NoteItem noteItem = app.CreateItem(OlItemType.olNoteItem).as(NoteItem.class); // Append Outlook-ID to note text. String body = noteText.getText() + CRLF + CRLF + "Outlook:" + mailItem.getEntryID(); noteItem.setBody(body); // Assign category to note String cats = category.toString(); noteItem.setCategories(cats); // Assign category to mail if checkbox is selected boolean saveMail = false; if (ckCatMail.isSelected()) { saveMail = !mailItem.getCategories().equals(cats); if (saveMail) { mailItem.setCategories(cats); } } // Save note noteItem.Save(); // Save mail if (saveMail) { mailItem.Save(); } // Display note if (ckDisplay.isSelected()) { noteItem.Display(false); } } catch (Throwable e) { e.printStackTrace(); } }); } } private final static String CRLF = "\n"; }